#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <modbus/modbus-rtu.h>
#include <mosquitto.h>
#include <cjson/cJSON.h>

const int base = 1000;

const char *wave_name[] = {"switch_off",
                           "switch_on ",
                           "store     ",
                           "current   ",
                           "vibration "};

static _Bool __signal = false;

static char *device = "/dev/ttyUSB0";
static char *topic = "/edgex/00000000";
static int pkgs = 32;
static int retry = 3;
static _Bool v1 = false;

static time_t last_time[5];

static struct mosquitto *mosq;
static modbus_t *modbus;

static void _sighandler(int sig) {
	printf("terminal with signal %d\n", sig);
	__signal = true;
}

int pub_to_mqtt(void *payload, int len) {
	int rc;

	rc = mosquitto_publish(mosq, NULL, topic, len, payload, 1, false);
	if (MOSQ_ERR_SUCCESS != rc) {
		fprintf(stderr, "mosquitto_publish = %d\n", rc);
	}

	return rc;
}

static int json_write_header(cJSON *json, int slave, int idx, time_t timestamp) {
	cJSON_AddStringToObject(json, "device", device + 5);
	cJSON_AddNumberToObject(json, "unit_id", slave);
	cJSON_AddNumberToObject(json, "wave_idx", idx);
	cJSON_AddNumberToObject(json, "timestamp", timestamp);

	return 0;
}

static int json_write_eng_dur(cJSON *json, uint16_t duration, uint16_t energy) {
	cJSON_AddNumberToObject(json, "duration", duration);
	cJSON_AddNumberToObject(json, "energy", energy);

	return 0;
}

float int_bits_to_float(uint8_t *vals) {
	return *(float *) (vals);
}

static int json_write_feature(cJSON *json, uint8_t *feature) {
	int i;

	cJSON *json_feature_t = cJSON_CreateArray();
	cJSON *json_feature_i = cJSON_CreateArray();
	for (i = 0; i < 12; i += 4) {
		cJSON_AddItemToArray(json_feature_t, cJSON_CreateNumber(int_bits_to_float(feature + i)));
		cJSON_AddItemToArray(json_feature_i, cJSON_CreateNumber(int_bits_to_float(feature + i + 12)));
	}
	cJSON_AddItemReferenceToObject(json, "feature_t", json_feature_t);
	cJSON_AddItemReferenceToObject(json, "feature_i", json_feature_i);

	return 0;
}

static int json_write_wave(cJSON *json, int16_t *wave_data) {
	int i;

	cJSON *json_wave = cJSON_CreateArray();
	for (i = 0; i < 64 * 6; ++i) {
		cJSON_AddItemToArray(json_wave, cJSON_CreateNumber(wave_data[i]));
	}
	cJSON_AddItemReferenceToObject(json, "wave", json_wave);

	return 0;
}

static int json_send(cJSON *json, int idx, time_t timestamp) {
	int rc;
	char *jsonstr = cJSON_Print(json);

	last_time[idx] = timestamp;

	printf("send `%s` to topic `%s`, payload=%s\n", wave_name[idx], topic, jsonstr);
	rc = pub_to_mqtt(jsonstr, strlen(jsonstr));

	cJSON_free(jsonstr);

	return rc;
}

static time_t modbus_register_to_time(const uint8_t *time) {
	struct tm _tm;

	if (time[0] == 0 && time[1] == 0 && time[2] == 0 && time[3] == 0 && time[4] == 0 && time[5] == 0) {
		return 0;
	}

	_tm.tm_year = 100 + time[1];
	_tm.tm_mon = time[0] - 1;
	_tm.tm_mday = time[3];
	_tm.tm_hour = time[2];
	_tm.tm_min = time[5];
	_tm.tm_sec = time[4];

	return mktime(&_tm);
}

static int read_wave_data(modbus_t *modbus, int off, int16_t *wave_data) {
	int rc, i, j;
	int pkgs_size = pkgs, pkgs_nums = (64 * 6) / pkgs_size;

	for (i = 0; i < pkgs_nums; ++i) {
		int addr = off + i * pkgs_size;
		for (j = 0; j < 3; ++j) {
			rc = modbus_read_registers(modbus, addr, pkgs_size, wave_data + i * pkgs_size);
			if (rc < 0) {
				fprintf(stderr, "modbus_read_registers (wave_addr=%d) = %d, retry\n", addr, rc);
			} else {
				break;
			}
		}
		if (rc < 0) {
			fprintf(stderr, "modbus_read_registers (wave_addr=%d) = %d\n", addr, rc);
			return rc;
		}
	}

	return rc;
}

int read_wave_v0(modbus_t *modbus, int slave, int idx) {
	int rc;
	int off = base + 1000 + idx * 400;
	uint16_t reg_header[16];
	int16_t wave_data[64 * 6];

	rc = modbus_read_registers(modbus, off, 16, (uint16_t *) reg_header);
	if (rc < 0) {
		fprintf(stderr, "modbus_read_registers (wave_head) = %d\n", rc);
		return rc;
	}

	time_t timestamp = modbus_register_to_time((uint8_t *) &reg_header[13]);
	printf("read `%s` wave, time:%s", wave_name[idx], ctime(&timestamp));
	if (timestamp == last_time[idx]) {
		return rc;
	}

	read_wave_data(modbus, off + 16, wave_data);

	cJSON *json = cJSON_CreateObject();

	json_write_header(json, slave, idx, timestamp);
	json_write_eng_dur(json, reg_header[0], reg_header[1]);
	json_write_wave(json, wave_data);

	json_send(json, idx, timestamp);

	cJSON_Delete(json);

	return rc;
}

int read_wave_v1(modbus_t *modbus, int slave, int idx) {
	int rc;
	int off = base + 1000 + idx * 1000;
	uint16_t reg_header[18];
	int16_t wave_data[64 * 6];

	rc = modbus_read_registers(modbus, off, 18, (uint16_t *) reg_header);
	if (rc < 0) {
		fprintf(stderr, "modbus_read_registers (wave_head) = %d\n", rc);
		return rc;
	}

	time_t timestamp = modbus_register_to_time((uint8_t *) &reg_header[2]);
	printf("read `%s` wave, time:%s", wave_name[idx], ctime(&timestamp));
	if (timestamp == last_time[idx]) {
		return rc;
	}

	read_wave_data(modbus, off + 100, wave_data);

	cJSON *json = cJSON_CreateObject();

	json_write_header(json, slave, idx, timestamp);
	json_write_eng_dur(json, reg_header[0], reg_header[1]);
	json_write_feature(json, (uint8_t *) &reg_header[6]);
	json_write_wave(json, wave_data);

	json_send(json, idx, timestamp);

	cJSON_Delete(json);

	return rc;
}

int loop(int slave) {
	int rc, i;

	rc = mosquitto_reconnect(mosq);
	if (-1 == rc) {
		fprintf(stderr, "mosquitto_reconnect = %d\n", rc);
		return rc;
	}

	rc = modbus_connect(modbus);
	if (-1 == rc) {
		fprintf(stderr, "modbus_connect = %d\n", rc);
		return rc;
	}
	modbus_set_slave(modbus, slave);

	uint16_t file_nums[8];
	rc = modbus_read_registers(modbus, base + 21, 8, file_nums);
	if (rc < 0) {
		fprintf(stderr, "modbus_read_registers (file_nums) = %d\n", rc);
		return rc;
	}

	for (i = 0; i < 5; ++i) {
		printf("%s=%d, ", wave_name[i], file_nums[i + 3]);
	}
	printf("\n");

	for (i = 0; i < 5; ++i) {
		if (file_nums[i + 3] > 0) {
			if (v1) {
				rc = read_wave_v1(modbus, slave, i);
			} else {
				rc = read_wave_v0(modbus, slave, i);
			}
			if (rc >= 0) {
				int off = base + 24 + i;
				rc = modbus_write_register(modbus, off, 0xffff);
				fprintf(stderr, "modbus_write_register (del_wave[%d]) = %d\n", off, rc);
			}
		}
	}

	modbus_close(modbus);
	mosquitto_disconnect(mosq);

	return rc;
}

int main(int argc, char *argv[]) {
	int rc, i;
	int slave = 1;
	_Bool debug = false;

	char *host = "localhost";
	int port = 1883;
	char *username = "guest";
	char *password = "guest";

	int delay = 10;

	for (i = 1; i < argc; ++i) {
		if (!strcasecmp("--delay", argv[i])) {
			delay = atoi(argv[++i]);
			if (delay < 1 || delay > 600) {
				delay = 10;
			}
		} else if (!strcasecmp("--pkgs", argv[i])) {
			pkgs = atoi(argv[++i]);
			if (pkgs < 32 || pkgs > 96) {
				pkgs = 32;
			}
		} else if (!strcasecmp("--retry", argv[i])) {
			retry = atoi(argv[++i]);
			if (retry < 1 || retry > 5) {
				retry = 3;
			}
		} else if (!strcasecmp("--device", argv[i])) {
			device = argv[++i];
		} else if (!strcasecmp("--slave", argv[i])) {
			slave = atoi(argv[++i]);
		} else if (!strcasecmp("--host", argv[i])) {
			host = argv[++i];
		} else if (!strcasecmp("--port", argv[i])) {
			port = atoi(argv[++i]);
		} else if (!strcasecmp("--topic", argv[i])) {
			topic = argv[++i];
		} else if (!strcasecmp("--username", argv[i])) {
			username = argv[++i];
		} else if (!strcasecmp("--password", argv[i])) {
			password = argv[++i];
		} else if (!strcasecmp("--v1", argv[i])) {
			v1 = true;
		} else if (!strcasecmp("--debug", argv[i])) {
			debug = true;
		} else {
			fprintf(stderr, "usage: rtelec --debug --v1 --pkgs (32) --retry (3)\n"
			                "\t--device (/dev/ttyUSB0) --slave slave_id (1)\n"
			                "\t--host (localhost) --port (1883) --topic (/edgex/00000000) --username (guest) --password (guest)\n");
			return EXIT_FAILURE;
		}
	}

	printf("waiting for wave, device=%s, slave=%d, retry=%d, pkgs=%d\n", device, slave, retry, pkgs);

	mosquitto_lib_init();
	mosq = mosquitto_new("rtelec", false, NULL);
	mosquitto_username_pw_set(mosq, username, password);
	rc = mosquitto_connect(mosq, host, port, 60);
	if (MOSQ_ERR_SUCCESS != rc) {
		fprintf(stderr, "mosquitto_connect = %d\n", rc);
	} else {
		mosquitto_disconnect(mosq);
	}

	struct timeval _tm = {1, 0};
	modbus = modbus_new_rtu(device, 115200, 'N', 8, 1);
	modbus_set_response_timeout(modbus, &_tm);
	modbus_set_error_recovery(modbus, MODBUS_ERROR_RECOVERY_LINK | MODBUS_ERROR_RECOVERY_PROTOCOL);
	modbus_set_debug(modbus, debug);

//	signal(SIGINT, _sighandler);
//	signal(SIGTERM, _sighandler);
//
//	while (!__signal) {
//	for (i = 0; i < delay; ++i) {
//		if (__signal) {
//			break;
//		}
//		sleep(1);
//	}
	loop(slave);
//	}

	modbus_free(modbus);

	mosquitto_destroy(mosq);
	mosquitto_lib_cleanup();

	return EXIT_SUCCESS;
}